#region Using

using System;
using Microsoft.Xna.Framework;
using XNgine.Misc;

#endregion

namespace XNgine.CameraManagement
{
    public class Camera : ICamera
    {
        #region INamedItem Members

        public string Name { get; private set; }

        #endregion

        #region FOV, NearPlane, FarPlane

        private float _fieldOfView = MathHelper.Pi / 3.0f;
        /// <summary>
        /// The viewable angle.
        /// </summary>
        public float FieldOfView
        {
            get { return _fieldOfView; }
            set { _fieldOfView = value; }
        }

        private float _nearPlane = 0.1f;
        /// <summary>
        /// The near plane used to determine the viewable area.
        /// </summary>
        public float NearPlane
        {
            get { return _nearPlane; }
            set { _nearPlane = value; }
        }

        private float _farPlane = 3500.0f;
        /// <summary>
        /// The far plane used to determine the viewable area.
        /// </summary>
        public float FarPlane
        {
            get { return _farPlane; }
            set { _farPlane = value; }
        }

        /// <summary>
        /// Slightly smaller viewable field of view for culling.
        /// </summary>
        public float ViewableFieldOfView
        {
            get { return _fieldOfView / 1.125f; }
        }

        #endregion

        #region Frustum

        /// <summary>
        /// The trapezoid that contains everything that the camera can see.
        /// </summary>
        public BoundingFrustum Frustum { get; private set; }

        #endregion

        #region Position, Orientation

        public Vector3 Position { get; set; }

        #endregion

        #region Target, Up (Other directional vectors)

        public Vector3 Target { get; set; }
        public Vector3 Up { get; set; }

        // There vectors are automaticly calculated using Position, Target and Up vectors
        public Vector3 Forward { get; private set; }
        public Vector3 Backward { get; private set; }
        public Vector3 Right { get; private set; }
        public Vector3 Left { get; private set; }
        public Vector3 Down { get; private set; }

        #endregion

        #region View, Projection

        public Matrix View { get; private set; }

        public Matrix Projection { get; private set; }

        #endregion

        #region Initialization

        public Camera(string name)
        {
            Name = name;

            Up = Vector3.Up;
            Target = Vector3.Forward; //new Vector3(0, 0, -1);
            Position = Vector3.Zero;

            UpdateMatrices();
        }

        #endregion

        #region Main loop

        public virtual void Update()
        {
            RecalculateDirectionalVectors();
            UpdateMatrices();
        }

        #endregion

        #region Private Helper Methods

        private void RecalculateDirectionalVectors()
        {
            Forward = Vector3.Normalize(Target - Position);
            //TODO: Find a way to fix problems that arise when Forward and UP are parallel
            Right = Vector3.Normalize(Vector3.Cross(Forward, Up));

            Backward = -Forward;
            Left = -Right;
            Down = -Up;

        }

        protected void UpdateMatrices()
        {
            GameSettings settings = XEngine.Instance.Settings;

            View = Matrix.CreateLookAt(Position, Target, Up);

            Projection = Matrix.CreatePerspectiveFieldOfView(_fieldOfView, settings.AspectRatio,
                                                              _nearPlane, settings.FarClippingDistance);

            Frustum = new BoundingFrustum(View * Projection);
        }

        #endregion
    }
}